/*
 * Created on 2004.08.11.
 */
package com.csaba.swing.gui.util;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.LayoutManager;

import javax.swing.SwingConstants;

/**
 * @author gyenes
 */
public class DynamicColumnLayout implements LayoutManager, SwingConstants
{
	private final int[] aligments;
	private int column = 0;
	private boolean setToPreferredSize = false;
	private int hpadding = 5;
	private int vpadding = 2;
	private boolean stretch = true;

	public DynamicColumnLayout( final int[] aligments )
	{
		this(aligments, true, true);
	}

	public DynamicColumnLayout( final int[] aligments, final boolean stretch, final boolean setToPreferredSize )
	{
		this.aligments = aligments;
		setStretch(stretch);
		setSetToPreferredSize(setToPreferredSize);

		this.column = aligments.length;
		if ( column < 1 )
			throw new IllegalArgumentException("Invalid number of columns: " + column);
	}

	public void removeLayoutComponent( final Component comp )
	{
		//do nothing
	}

	public void addLayoutComponent( final String name, final Component comp )
	{
		//do nothing
	}

	public void layoutContainer( final Container parent )
	{
		final int[][] maxes = getMaxes(parent);

		if ( maxes == null )
			return; // theres is nothing to do.

		//calculate preferred layout size 
		final Dimension prefSize = getPreferredSize(maxes[0], maxes[1]);

		final Dimension actSize = parent.getSize();

		//calculate best fitting layout
		final int vpadding = !stretch ? this.vpadding : spreadValues(maxes[1], actSize.height, prefSize.height,
				this.vpadding);
		final int hpadding = !stretch ? this.hpadding : spreadValues(maxes[0], actSize.width, prefSize.width,
				this.hpadding);

		//do actual layout
		final Component[] comps = parent.getComponents();
		int xLoc = 0;
		int yLoc = 0;
		for ( int i = 0; i < comps.length; i++ )
		{
			final Component comp = comps[i];

			final int colIndex = i % column;
			final int rowIndex = i / column;

			if ( colIndex == 0 )
			{
				xLoc = 0;
				if ( rowIndex > 0 )
					yLoc += maxes[1][rowIndex - 1] + vpadding;
			}

			if ( isSetToPreferredSize() )
			{
				final Dimension pref = comp.getPreferredSize();
				pref.width = Math.min(pref.width, maxes[0][colIndex]);
				pref.height = Math.min(pref.height, maxes[1][rowIndex]);

				// default horizontal aligment is left
				final int aligment = aligments == null ? LEFT : aligments[colIndex];
				int x = xLoc;
				switch ( aligment )
				{
				case CENTER:
					x += ( maxes[0][colIndex] - pref.width ) / 2;
					break;
				case RIGHT:
					x += maxes[0][colIndex] - pref.width;
					break;
				}

				final int y = yLoc + ( maxes[1][rowIndex] - pref.height ) / 2;
				comp.setSize(pref);
				comp.setLocation(x, y);
			}
			else
			{
				comp.setSize(maxes[0][colIndex], maxes[1][rowIndex]);
				comp.setLocation(xLoc, yLoc);
			}

			xLoc += maxes[0][colIndex] + hpadding;
		}
	}

	private int spreadValues( final int[] maxs, int actual, final int preferred, final int padding )
	{
		int remainder = 0;
		int modifiedPadding = padding;

		int componentSize = 0;
		for ( final int i : maxs )
		{
			componentSize += i;
		}

		//correct actual size with padding
		if ( actual < preferred )
		{
			modifiedPadding = ( actual - componentSize ) / maxs.length;
			if ( modifiedPadding < 0 )
				modifiedPadding = 0;

		}
		actual -= ( maxs.length - 1 ) * modifiedPadding;

		for ( int i = 0; i < maxs.length - 1; i++ )
		{
			if ( preferred == 0 )
			{//component size is not known: distribute equally
				final double a = ( ( (double) actual / (double) maxs.length ) * ( i + 1 ) );
				final double b = ( (double) actual / (double) maxs.length * i );
				maxs[i] = (int) Math.round(a - b);
			}
			else
			{
				maxs[i] = (int) Math.round((double) maxs[i] * (double) actual / componentSize);
			}
			remainder += maxs[i];
		}
		maxs[maxs.length - 1] = actual - remainder; //last item is size to the remainder
		return modifiedPadding;
	}

	private Dimension getPreferredSize( final int[] maxWidths, final int[] maxHeights )
	{
		final Dimension prefSize = new Dimension(0, 0);
		for ( int i = 0; i < maxWidths.length; i++ )
		{
			prefSize.width += maxWidths[i];
		}

		for ( int i = 0; i < maxHeights.length; i++ )
		{
			prefSize.height += maxHeights[i];
		}
		//adding padding to size
		prefSize.width += ( maxWidths.length - 1 ) * hpadding;
		prefSize.height += ( maxHeights.length - 1 ) * vpadding;
		return prefSize;
	}

	public Dimension minimumLayoutSize( final Container parent )
	{
		return preferredLayoutSize(parent);
	}

	public Dimension preferredLayoutSize( final Container parent )
	{
		final int[][] maxes = getMaxes(parent);
		if ( maxes == null )
			return new Dimension(0, 0);

		//calculate preferred layout size 
		final Dimension prefSize = getPreferredSize(maxes[0], maxes[1]);
		return prefSize;
	}

	private int[][] getMaxes( final Container parent )
	{
		final Component[] comps = parent.getComponents();
		if ( comps == null || comps.length == 0 )
			return null; // theres is nothing to do.
		final int rows = comps.length / column + 1;

		final int[] maxWidths = new int[column];
		final int[] maxHeights = new int[rows];

		//calculate preferred column/row size
		for ( int i = 0; i < comps.length; i++ )
		{
			final Component comp = comps[i];
			final Dimension compSize = comp.getPreferredSize();

			final int colIndex = i % column;
			final int rowIndex = i / column;

			maxWidths[colIndex] = Math.max(maxWidths[colIndex], compSize.width);
			maxHeights[rowIndex] = Math.max(maxHeights[rowIndex], compSize.height);
		}
		final int[][] result = new int[2][];
		result[0] = maxWidths;
		result[1] = maxHeights;

		return result;
	}

	public void setStretch( final boolean stretch )
	{
		this.stretch = stretch;
	}

	public boolean isStretch()
	{
		return stretch;
	}

	public void setSetToPreferredSize( final boolean setToPreferredSize )
	{
		this.setToPreferredSize = setToPreferredSize;
	}

	public boolean isSetToPreferredSize()
	{
		return setToPreferredSize;
	}

	public void setHpadding( final int hpadding )
	{
		this.hpadding = hpadding;
	}

	public int getHpadding()
	{
		return hpadding;
	}

	public void setVpadding( final int vpadding )
	{
		this.vpadding = vpadding;
	}

	public int getVpadding()
	{
		return vpadding;
	}

	private final static int[] STANDARD_FORM_LAYOUT = new int[] { DynamicColumnLayout.LEFT, DynamicColumnLayout.LEFT };

	/**
	 * It initialize the layout manager of the received container with a new Dynamic Layout manager having 2 columns:
	 * one for RIGHT justified, and an other for LEFT justified.
	 * @param container Container to set the layout manager.
	 */
	public static void initLayout( final Container container )
	{
		container.setLayout(getDefault());
	}

	/**
	 * This factory method produces an instance of this layout manager, with default form layout parameters.
	 * @return New layout manager instance with default parameters.
	 */
	public static LayoutManager getDefault()
	{
		return new DynamicColumnLayout(STANDARD_FORM_LAYOUT, false, true);
	}

}
